package org.hishenframework.core.data.jdbc.template;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;

import org.springframework.dao.support.DataAccessUtils;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.simple.ParameterizedBeanPropertyRowMapper;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.util.Assert;

/**
 * jdbc操作类，可以继承、可实例化
 * @author 张迪 
 * 2011-8-29 下午03:51:16
 * @param <T>
 */
public class JdbcOperater<T> {
//	private Logger logger = LoggerFactory.getLogger(JdbcOperater.class);
	//TODO: 增加查重方法
	/** 实体类型 */
	private Class<T> entityClass;
	/** 实体的所有方法 */
	private Method[] methods;
	/** 实体的所有属性 */
	private Field[] fields;
	/** hishen jdbc模板 */
	private AbstractHishenJdbcTemplate<T> hishenJdbcTemplate;
	/** jdbc插入 */
	private SimpleJdbcInsert simpleJdbcInsert;
	/** hishen jdbc更新 */
	private HishenSimpleJdbcUpdate hishenSimpleJdbcUpdate;
	/** hishen jdbc删除 */
	private HishenSimpleJdbcDelete hishenSimpleJdbcDelete;
	/** hishen jdbc查询 */
	private HishenSimpleJdbcQuery<T> hishenSimpleJdbcQuery;
	
	/** 是否是自增主键 */
	private GenerationType generationType;
	
	/** 表名称 */
	private String tableName;
	
	/** 查询所有数据 */
	private String selectAllSql;
	
	/** 单主键查询语句 */
	private String simplePkSelectOneResultSql;
	
	/** 单主键删除语句 */
	private String simplePkDeleteSql;

	/** 创建查询子类 */
	private Query query = new Query();
	
	/**
	 * 无参数实例化，只能继承使用，否则会出错。
	 * @param dataSource
	 */
	public JdbcOperater() {
		entityClass = getActualTypeArgument(getClass());
	}
	
	/**
	 * 获取泛型类型
	 * @param clazz
	 * @return
	 */
	private Class<T> getActualTypeArgument(Class<?> clazz) {
		Type type = clazz.getGenericSuperclass();
		if (type instanceof ParameterizedType) {
			return (Class<T>) ((ParameterizedType) type).getActualTypeArguments()[0];
		} else {
			return getActualTypeArgument(clazz.getSuperclass()); 
		}
	}
	
	/**
	 * 配合JdbcOperater()继承构造方法使用
	 * @param freezeJdbcTemplate
	 */
	@Resource
	public void setJdbcTemplate(AbstractHishenJdbcTemplate<T> freezeJdbcTemplate) {
		this.hishenJdbcTemplate = freezeJdbcTemplate;
		init(entityClass);
	}
	
	/**
	 * 实例化时使用
	 * @param entityClass
	 * @param hishenJdbcTemplate
	 */
	public JdbcOperater(Class<T> entityClass, AbstractHishenJdbcTemplate<T> hishenJdbcTemplate) {
		this.entityClass = entityClass; 
		this.hishenJdbcTemplate = hishenJdbcTemplate;
		init(entityClass);
	}
	
	/**
	 * 初始化
	 * 判断是否是实体、设置表名、设置jdbcInsert主键、设置单主键实体的SQL语句
	 * @param entityClass
	 */
	private void init(Class<T> entityClass) {
		//TODO: 验证只有一个自增主键
//		queryForBean = new QueryForBean();
		Entity entityAnn = entityClass.getAnnotation(Entity.class);
		Assert.notNull(entityAnn, entityClass.getName() + "不是实体");
		//获取表名称
		Table annTable = (Table) entityClass.getAnnotation(Table.class);
		if (annTable ==  null || tableName ==  annTable.name()) {
			tableName = entityClass.getSimpleName();
		} else {
			tableName = annTable.name();
		}
		//如果没有设置默认值则表明是类名
		if(tableName.length() == 0) {
			tableName = entityClass.getName().toLowerCase();
		}
		//获取所有属性
		ArrayList<Field> fieldList = new ArrayList<Field>();
		ArrayList<Method> methodList = new ArrayList<Method>();
		
		//获取所有父类的方法和属性
		Class<?> tempclazz = entityClass;
		do {
			for (Field field : tempclazz.getDeclaredFields()) {
				//如果属性不存在，则添加该属性
				if (!fieldContains(fieldList, field)) {
					fieldList.add(field);
				}
			}
			for (Method method : tempclazz.getMethods()) {
				//如果方法不存在，则添加该方法
				if (!methodContains(methodList, method)) {
					methodList.add(method);
				}
			}
			tempclazz = tempclazz.getSuperclass();
		}
		while (!tempclazz.equals(Object.class));
		
		//获取所有方法
		fields = fieldList.toArray(new Field[fieldList.size()]);
		methods = methodList.toArray(new Method[methodList.size()]);
		
		//主键
		ArrayList<String> primaryKeys = new ArrayList<String>();
		//自增列
		ArrayList<String> identityColumns = new ArrayList<String>();
		for (Field field : fields) {
			Id id = field.getAnnotation(Id.class);
			if(id != null) {
				primaryKeys.add(JpaJdbcUtils.upperCaseToUnderline(field.getName()));
			}
			GeneratedValue generatedValue = field.getAnnotation(GeneratedValue.class);
			if (generatedValue != null) {
				generationType = generatedValue.strategy();
				if(generatedValue.strategy() == GenerationType.IDENTITY) {
					identityColumns.add(JpaJdbcUtils.upperCaseToUnderline(field.getName()));
				}
			}
		}
		//设置插入模板
		simpleJdbcInsert = new SimpleJdbcInsert(hishenJdbcTemplate.getJdbcTemplate());		
		simpleJdbcInsert.withTableName(tableName).usingGeneratedKeyColumns(identityColumns.toArray(new String[identityColumns.size()]));
		
		//设置更新模板
		hishenSimpleJdbcUpdate = new HishenSimpleJdbcUpdate(hishenJdbcTemplate.getJdbcTemplate());
		hishenSimpleJdbcUpdate.withTableName(tableName).usingGeneratedKeyColumns(primaryKeys);
		
		//设置删除模板
		hishenSimpleJdbcDelete = new HishenSimpleJdbcDelete(hishenJdbcTemplate.getJdbcTemplate());
		hishenSimpleJdbcDelete.withTableName(tableName);
		
		//设置简单查询模板
		hishenSimpleJdbcQuery = new HishenSimpleJdbcQuery<T>(hishenJdbcTemplate.getJdbcTemplate(), entityClass);
		hishenSimpleJdbcQuery.withTableName(tableName);
		
		//为单主键表预生成语句
		if (primaryKeys.size() == 1) {
			simplePkSelectOneResultSql = "SELECT * FROM " + tableName
					+ " WHERE " + primaryKeys.get(0) + " = ?";
			simplePkDeleteSql = "DELETE FROM " + tableName 
					+ " WHERE " + primaryKeys.get(0) + " = ?";
		}
		//TODO: 增加一个多主键实体获取语句
		selectAllSql = "SELECT * FROM " + tableName;
	}
	
	/**
	 * 保存（返回影响行数）
	 * @param entity
	 * @return
	 */
	public long save(T entity) {
		long genPk = 0;
		HashMap<String, Object> args = new HashMap<String, Object>();
		for (Field field : fields) {
			//pass不映射字段
			Transient transient1 = field.getAnnotation(Transient.class);
			if (transient1 != null) {
				continue;
			}
			
			//pass不插入字段
			Column column = field.getAnnotation(Column.class);
			if (column != null && !column.insertable()) {
				continue;
			}
			
			//pass自增字段
			GeneratedValue generatedValue = field.getAnnotation(GeneratedValue.class);
			if(generatedValue != null && generatedValue.strategy() == GenerationType.IDENTITY) {
				continue;
			}

			Object value = null;
			if (generatedValue != null && generatedValue.strategy() == GenerationType.TABLE) {
				genPk = hishenJdbcTemplate.newGen(entityClass, field);
				value = genPk;
			} else {
				try {
					//将列名和值放入参数map中
					value = getValue(entity, field, methods);
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			}
			args.put(getColumnName(field), value);
		}
		
		switch (generationType) {
		case TABLE:
			simpleJdbcInsert.execute(args);
			return genPk;
		case IDENTITY:
			Number identityPK = simpleJdbcInsert.executeAndReturnKey(args);
			return identityPK.longValue();
		default:
			return simpleJdbcInsert.execute(args);
		}
	}
	
	/**
	 * 更新
	 * @return 影响数量
	 */
	public int update(T entity) {
		Map<String, Object> args = new HashMap<String, Object>();
		for (Field field : fields) {
			//pass不映射字段
			Transient transient1 = field.getAnnotation(Transient.class);
			if (transient1 != null) {
				continue;
			}
			//pass不更新字段
			Column column = field.getAnnotation(Column.class);
			if (column != null && !column.updatable()) {
				continue;
			}
			try {
				Object value = getValue(entity, field, methods);
				args.put(getColumnName(field), value);
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		return hishenSimpleJdbcUpdate.execute(args);
	}

	/**
	 * 删除
	 * @param entity
	 * @return
	 */
	public int delete(T entity) {
		Map<String, Object> args = new HashMap<String, Object>();
		for (Field field : fields) {
			//查找主键字段
			Id id = field.getAnnotation(Id.class);
			if (id != null) {
				try {
					Object value = getValue(entity, field, methods);
					args.put(getColumnName(field), value);
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			}
		}
		return hishenSimpleJdbcDelete.execute(args);
	}
	
	/**
	 * 单主键删除
	 * @param id
	 * @return
	 */
	public int delete(int id) {
		if (simplePkDeleteSql == null) {
			throw new RuntimeException("不是单主键实体无法用该方法删除");
		}
		
		return hishenJdbcTemplate.getJdbcTemplate().update(simplePkDeleteSql, id);
	}
	
	/**
	 * 单主键多记录删除
	 * @param ids
	 * @return
	 */
	public int delete(Object... pks) {
		int count = 0;
		for (Object pk : pks) {
			count += delete(pk);
		}
		return count;
	}
	
	/**
	 * 获取一条记录
	 * @param ids
	 * @return
	 */
	public T get(Map<String, Object> pks) {
		return hishenSimpleJdbcQuery.execute(pks);
	}
	
	/**
	 * 获取一条记录
	 * @param ids
	 * @return
	 */
	public Map<String, Object> getForMap(Map<String, Object> pks) {
		return hishenSimpleJdbcQuery.executeForMap(pks);
	}
	
	/**
	 * 获取一条记录(单主键)
	 * @param id
	 * @return
	 */
	public T get(Object pk) {
		Assert.notNull(simplePkSelectOneResultSql, "不是单主键实体");
		RowMapper<T> rowMapper = ParameterizedBeanPropertyRowMapper.newInstance(entityClass);
		List<T> results = hishenJdbcTemplate.getJdbcTemplate().query(simplePkSelectOneResultSql, rowMapper, pk);
		return DataAccessUtils.singleResult(results);
	}
	
	/**
	 * 获取一条记录(单主键)
	 * @param id
	 * @return
	 */
	public Map<String, Object> getForMap(Object pk) {
		Assert.notNull(simplePkSelectOneResultSql, "不是单主键实体");
		return hishenJdbcTemplate.getJdbcTemplate().queryForMap(simplePkSelectOneResultSql, pk);
	}

	/**
	 * 获取所有记录
	 * @return
	 */
	public List<T> getAll() {
		return hishenJdbcTemplate.queryForListBean(selectAllSql, entityClass);
	}
	
	/**
	 * 获取所有记录
	 * @return
	 */
	public List<Map<String, Object>> getAllForMap() {
		return hishenJdbcTemplate.queryForListMap(selectAllSql);
	}
	
	
	/**
	 * 查询
	 * @return
	 */
	public Query query() {
		return query;
	}
	
	/**
	 * 查询
	 * @author Panda
	 *
	 */
	public class Query {
		/**
		 * 根据列名查询结果
		 * @param columnName 列名
		 * @param value 列值
		 * @return 实体集合
		 */
		public List<T> qSingleColumn(String columnName, String value) {
			Map<String, Object> args = new HashMap<String, Object>(1);
			args.put(columnName, value);
			return hishenSimpleJdbcQuery.executeForList(args);
		}
		
		/**
		 * 根据列名查询结果
		 * @param columnName 列名
		 * @param value 列值
		 * @return Map集合
		 */
		public List<Map<String, Object>> qSingleColumnListMap(String columnName, String value) {
			Map<String, Object> args = new HashMap<String, Object>(1);
			args.put(columnName, value);
			return hishenSimpleJdbcQuery.executeForListMap(args);
		}
		
		/**
		 * 查询并填充page对象
		 * @param page
		 * @param sql
		 * @param values
		 */
		public void qDataPaging(DataPaging<T> page, String sql, Object... values) {
			hishenJdbcTemplate.queryForDataPaging(page, sql, entityClass, values);
		}
		
		/**
		 * 查询并填充page中对象（map形式）
		 * @param page
		 * @param sql
		 * @param values
		 */
		public void qDataPagingMap(DataPaging<Map<String, Object>> page, String sql, Object... values) {
			hishenJdbcTemplate.queryForDataPaging(page, sql, values);
		}
		
		/**
		 * 查询并获取实体
		 * @param sql
		 * @param offset
		 * @param length
		 * @param values
		 * @return
		 */
		public List<T> qListBean(long offset, long length, String sql, Object... values) {
			return hishenJdbcTemplate.queryForListBean(offset, length, sql, entityClass, values);
		}
		
		public List<T> qListBean(String sql, Object... values) {
			return hishenJdbcTemplate.queryForListBean(sql, entityClass, values);
		}
		
		/**
		 * 查询单条记录并以bean的方式返回
		 * @param sql
		 * @param values
		 * @return
		 */
		public T qBean(String sql, Object... values) {
			return hishenJdbcTemplate.queryForBean(sql, entityClass, values);
		}
		
		/**
		 * 查询单条记录并以map的方式返回
		 * @param sql
		 * @param values
		 * @return
		 */
		public Map<String, Object> qMap(String sql, Object... values) {
			return hishenJdbcTemplate.getJdbcTemplate().queryForMap(sql, values);
		}
		
	}
	
	//util
	/**
	 * 获取字段名称
	 * @param field
	 * @return
	 */
	private String getColumnName(Field field) {
		Column column = field.getAnnotation(Column.class);
		if (column == null || column.name().length() == 0) {
			return field.getName();
		}
		return column.name();
	}

	/**
	 * 获取属性值
	 * @param entity
	 * @param methods
	 * @param field
	 * @return
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	private Object getValue(Object entity, Field field, Method[] methods) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		boolean isGetValue = false;
		Object result = null;
		//将属性名称格式处理成方法名称格式
		String upFieldName = field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
		//对布尔类型属性的特殊处理
		if(field.getType().equals(boolean.class)) {
			String isMethod = "is" + upFieldName;
			for (Method method : methods) {
				if(isMethod.equals(method.getName())) {
					result = method.invoke(entity);
					isGetValue = true;
					break;
				}
			}
		} else {
			String getMethod = "get" + upFieldName;
			for (Method method : methods) {
				if(getMethod.equals(method.getName())) {
					result = method.invoke(entity);
					isGetValue = true;
					break;
				}
			}
		}
		//属性没有取值方法的,将直接从属性上取值
		if(!isGetValue) {
			field.setAccessible(true);
			result = field.get(entity);
		}
		return result;
	}
	
	/**
	 * 属性是否存在
	 * @param fieldList
	 * @param field
	 * @return
	 */
	private boolean fieldContains(List<Field> fieldList, Field field) {
		for (Field field2 : fieldList) {
			if (field2.getName().equals(field.getName())) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * 方法是否存在
	 * @param methodList
	 * @param method
	 * @return
	 */
	private boolean methodContains(List<Method> methodList, Method method) {
		for (Method method2 : methodList) {
			if (method2.getName().equals(method.getName())) {
				return true;
			}
		}
		return false;
	}
	
	//get
	
	public AbstractHishenJdbcTemplate<T> getHishenJdbcTemplate() {
		return hishenJdbcTemplate;
	}
	
	public JdbcTemplate getJdbcTemplate() {
		return hishenJdbcTemplate.getJdbcTemplate();
	}
}
